const path = require('path');
const fs = require('fs');
const ejs = require("ejs");
const parser = require("@babel/parser");

const babylon = require('babylon');
const types = require("@babel/types");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const babel = require('@babel/core')
const { SyncHook } = require("tapable"); // 发布订阅

//babylon
// @babel/traverse
// @babel/types
// @babel/generator

class Compiler {
    static ID = 0;
    constructor(config) {
        this.config = config;
        this.entry = config.entry;
        // 当前目录
        this.root = process.cwd();
        this.modules = {}; // 保存所有模块
        this.ID = 0;

        this.hooks = {
            entryOption: new SyncHook(), // 开始
            compile: new SyncHook(), // 编译
            afterCompile: new SyncHook([content]), // 编译后
            run: new SyncHook(), // 运行
            emit: new SyncHook(), // 完成
            done: new SyncHook()
        }
        let plugins = this.config.plugins;
        if (Array.isArray(plugins)) {
            plugins.forEach(p => {
                p.apply(this);
            })
        }
    }

    /**
     * 读取文件内容，也就是源代码
     * @param {*} modulePath 
     */
    getSource(modulePath) {
        // return fs.readFileSync(modulePath, "utf8");
        // for loader
        let rules = this.config.module.rules;
        let content = fs.readFileSync(modulePath, "utf8");
        for (let i = 0; i < rules.length; i++) {
            let rule = rules[i];
            let { test, use } = rule;
            let len = use.length - 1; // loader的总长度
            // 判断是不是less
            if (test.test(modulePath)) {
                function normalLoader() {
                    // 是less
                    let loader = require(use[len--]);
                    content = loader(content);
                    console.log(content);
                    if (len >= 0) {
                        normalLoader();
                    }
                }
                normalLoader();
            }
        }
        return content;
    }

    /**
     * 模块文件解析
     * @param {文件内容} source 
     * @param {文件目录} parentPath 
     */
    parse(source, parentPath) {
        // const code = "const n = 1;";

        const ast = babylon.parse(source);
        let dependencies = [];
        // console.log(ast);
        traverse(ast, {
            CallExpression(cpath) {
                let node = cpath.node;
                if (node.callee.name === "require") {
                    node.callee.name = "__webpack_require__";
                    let moduleName = node.arguments[0].value;
                    moduleName = moduleName + (path.extname(moduleName) ? "" : ".js");
                    moduleName = "./" + path.join(parentPath, moduleName);
                    dependencies.push(moduleName);
                    node.arguments = [types.stringLiteral(moduleName)];
                }
            }
            // enter(path) {
            //     if (path.isIdentifier({name: "n"})) {
            //         console.log(path);
            //     }
            // }
        });
        let sourceCode = generator(ast).code;
        // console.log(sourceCode);
        return { sourceCode, dependencies };
    }

    // 从root节点找所有依赖的模块
    /**
     * 
     * @param {模块文件路径} modulePath 
     * @param {是否是入口文件} isEntry 
     */
    buildModule(modulePath, isEntry) {
        // console.log(modulePath);
        let source = this.getSource(modulePath);
        let moduleName = "./" + path.relative(this.root, modulePath);
        // console.log(moduleName);
        if (isEntry) {
            this.entryId = moduleName;
        }
        // 先解析当前文件，
        let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName));
        // 保存解析的结果
        this.modules[moduleName] = sourceCode;
        // 再递归查找子模块进行解析
        dependencies.forEach(dep => {
            this.buildModule(path.join(this.root, dep), false);
        });
    }

    // 打包文件
    emitFile() {
        let main = path.join(this.config.output.path, this.config.output.filename);
        let templateStr = this.getSource(path.join(__dirname, "bundle.ejs"));
        let result = ejs.render(templateStr, {
            entryId: this.entryId,
            modules: this.modules
        });
        this.assets = {};
        this.assets[main] = result;
        // console.log(main);
        fs.writeFileSync(main, this.assets[main]);
    }

    // 执行
    run() {
        this.hooks.run.call(); // 消费、
        this.hooks.compile.call(); // 编译
        this.buildModule(path.resolve(this.root, this.entry), true);
        this.hooks.afterCompile.call(); // 编译完成
        this.hooks.emit.call();
        this.emitFile();
        this.hooks.done.call();
    }
}

module.exports = Compiler;